package com.junxonline.instant.admin.service.config.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.dao.entity.dto.common.*;
import com.junxonline.instant.dao.entity.model.config.*;
import com.junxonline.instant.dao.entity.model.log.LogLogin;
import com.junxonline.instant.dao.entity.vo.config.UserVO;
import com.junxonline.instant.dao.mapper.config.*;
import com.junxonline.instant.admin.service.config.UserService;
import com.junxonline.instant.common.annotation.DataScope;
import com.junxonline.instant.common.constant.*;
import com.junxonline.instant.common.entity.common.CommonReqPage;
import com.junxonline.instant.common.entity.common.CommonRespPage;
import com.junxonline.instant.common.enumeration.AdminErrorEnum;
import com.junxonline.instant.common.enumeration.LoginLogEnum;
import com.junxonline.instant.common.exception.AdminBizException;
import com.junxonline.instant.common.util.admin.TokenUtils;
import com.junxonline.instant.util.NetworkUtils;
import com.junxonline.instant.util.PasswordUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 用户管理接口实现类
 *
 * @author JunX
 * @date 2021-06-24
 */
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private TokenUtils tokenUtils;

    @Autowired
    private SettingMapper settingMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Value("${security.defaultPassword}")
    private String defaultPassword;

    /**
     * 查询用户列表
     *
     * @param vo      请求参数
     * @param wrapper 条件查询器
     * @return CommonRespPage<User>
     */
    @Override
    @DataScope
    public CommonRespPage<User> queryList(CommonReqPage<UserVO> vo, QueryWrapper<User> wrapper) {
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getUsername()), "t.username", vo.getQueryParams().getUsername());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getNickname()), "t.nickname", vo.getQueryParams().getNickname());
        wrapper.eq(vo.getQueryParams().getStatus() != null, "t.status", vo.getQueryParams().getStatus());
        wrapper.eq(vo.getQueryParams().getGender() != null, "t.gender", vo.getQueryParams().getGender());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getPhone()), "t.phone", vo.getQueryParams().getPhone());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getEmail()), "t.email", vo.getQueryParams().getEmail());
        wrapper.in(vo.getQueryParams().getOrganizationId() != null, "t.organization_id", vo.getQueryParams().getOrganizationId());
        wrapper.ne("t.del", CommonConstant.NO);
        IPage<User> iPage = userMapper.queryList(new Page<>(vo.getPage(), vo.getPageSize()), wrapper);
        return CommonRespPage.success(iPage);
    }

    /**
     * 查询用户
     *
     * @param id ID
     * @return User
     */
    @Override
    public User query(Long id) {
        User user = userMapper.selectById(id);
        Assert.notNull(user, AdminErrorEnum.DATA_IS_NOT_EXISTS.getMsg());
        // 查询用户角色
        List<UserRole> userRoles = userRoleMapper.selectList(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, user.getId()));
        List<String> roles = new ArrayList<>();
        for (UserRole userRole : userRoles) {
            roles.add(String.valueOf(userRole.getRoleId()));
        }
        user.setRoles(roles);
        // 查询用户角色并拼接角色名称
        StringBuilder roleNamesString = new StringBuilder();
        int index = 0;
        for (String roleId : roles) {
            Role role = roleMapper.selectById(Long.valueOf(roleId));
            roleNamesString.append("[" + role.getOrganizationName() + "]" + role.getName());
            if (index < roles.size() - 1) {
                roleNamesString.append(", ");
            }
            index++;
        }
        user.setRoleNames(roleNamesString.toString());
        return user;
    }

    /**
     * 新增用户
     *
     * @param user 请求参数
     */
    @Override
    public void add(User user) {
        Integer count = userMapper.selectCount(new LambdaQueryWrapper<User>()
                .eq(User::getUsername, user.getUsername()));
        if (count > 0) {
            throw new AdminBizException(AdminErrorEnum.DATA_IS_EXISTS);
        }
        // 获取雪花ID
        user.setId(YitIdHelper.nextId());
        String securityPassword = PasswordUtils.encrypt(DigestUtil.md5Hex(defaultPassword));
        user.setPassword(securityPassword);
        user.setAvatar("https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png");
        user.setDel(1);
        userMapper.insert(user);
        // 保存用户角色
        List<String> roleIds = user.getRoles();
        for (String roleId : roleIds) {
            userRoleMapper.insert(new UserRole(user.getId(), Long.valueOf(roleId)));
        }
    }

    /**
     * 编辑用户
     *
     * @param user 请求参数
     */
    @Override
    public void edit(User user) {
        Integer count = userMapper.selectCount(new LambdaQueryWrapper<User>()
                .ne(User::getId, user.getId())
                .eq(User::getUsername, user.getUsername()));
        if (count > 0) {
            throw new AdminBizException(AdminErrorEnum.DATA_IS_EXISTS);
        }
        userMapper.updateById(user);
        // 删除用户角色
        userRoleMapper.delete(new LambdaUpdateWrapper<UserRole>().eq(UserRole::getUserId, user.getId()));
        // 保存用户角色
        List<String> roleIds = user.getRoles();
        for (String roleId : roleIds) {
            userRoleMapper.insert(new UserRole(user.getId(), Long.valueOf(roleId)));
        }
    }

    /**
     * 删除用户
     *
     * @param ids 请求参数
     */
    @Override
    public void delete(List<Long> ids) {
        userMapper.deleteBatchIds(ids);
    }

    /**
     * 重置用户密码
     *
     * @param id ID
     */
    @Override
    public void resetPassword(Long id) {
        User user = userMapper.selectById(id);
        String securityPassword = PasswordUtils.encrypt(DigestUtil.md5Hex(defaultPassword));
        user.setPassword(securityPassword);
        userMapper.updateById(user);
    }

    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return UserInfoDTO
     */
    @Override
    public UserInfoDTO login(String username, String password) {
        // 校验用户名与密码
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username).last(SQLConstant.LIMIT_1));
        Assert.notNull(user, AdminErrorEnum.ACCOUNT_USERNAME_PASSWORD_ERROR.getMsg());
        Assert.isTrue(user.getStatus() == 1, AdminErrorEnum.ACCOUNT_DISABLED_ERROR.getMsg());

        boolean isSuccess = PasswordUtils.valid(password, user.getPassword());
        Assert.isTrue(isSuccess, AdminErrorEnum.ACCOUNT_USERNAME_PASSWORD_ERROR.getMsg());

        Long currentTime = DateUtil.current();
        String token = tokenUtils.sign(user.getId(), username, password, currentTime);
        // 存入redis
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_TOKEN, token);
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_REFRESH_TOKEN, currentTime);
        // 获取相关信息后存入Redis的在线用户列表中
        String userIp = NetworkUtils.getIpAddress(request);
        String targetIp = request.getHeader("Host");
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_USER_ID, user.getId());
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_USERNAME, user.getUsername());
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_USER_IP, userIp);
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_TARGET_IP, targetIp);
        UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_BROWSER, ua.getBrowser().toString());
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_OS, ua.getOs().toString());
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_CREATE_TIME, currentTime);
        redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_UPDATE_TIME, currentTime);
        // 设定过期时间
        redisUtils.expire(RedisConstant.KEY_ONLINE_USER + username, tokenUtils.getRefreshTokenExpireTime());

        // Redis消息队列发送登录日志消息
        LogLogin logLogin = this.getLoginLog(username, LoginLogEnum.LOGIN);
        if (logLogin != null) {
            redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_LOG_LOGIN, JSON.toJSONString(logLogin));
        }

        // 组装数据返回给前端
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        userInfoDTO.setToken(token);
        userInfoDTO.setNickname(user.getNickname());
        userInfoDTO.setAvatar(user.getAvatar());

        LinkedHashSet<RoleInfoDTO> roleSet = new LinkedHashSet<>();
        LinkedHashSet<Menu> menuSet = new LinkedHashSet<>();
        LinkedHashSet<Menu> buttonSet = new LinkedHashSet<>();
        // 查找用户拥有的角色
        List<Role> roles = roleMapper.selectByUserId(user.getId());
        for (Role role : roles) {
            roleSet.add(new RoleInfoDTO(role.getName()));
            List<Menu> menuList = menuMapper.selectByRoleId(role.getId());
            for (Menu menu : menuList) {
                Integer type = menu.getType();
                if (type == 0 || type == 1) {
                    menuSet.add(menu);
                } else if (type == 2) {
                    buttonSet.add(menu);
                }
            }
        }
        userInfoDTO.setRoleInfos(roleSet);
        // 组装权限
        LinkedHashSet<PermissionInfoDTO> permissionSet = new LinkedHashSet<>();
        for (Menu menu : menuSet) {
            PermissionInfoDTO permissionInfoDTO = new PermissionInfoDTO();
            Long menuId = menu.getId();
            permissionInfoDTO.setId(menu.getCode());
            permissionInfoDTO.getOperation().add(menu.getPermission());
            for (Menu button : buttonSet) {
                Long buttonParentId = button.getParentId();
                if (buttonParentId.equals(menuId)) {
                    permissionInfoDTO.getOperation().add(button.getPermission());
                }
            }
            permissionSet.add(permissionInfoDTO);
        }
        userInfoDTO.setPermissionInfos(permissionSet);
        return userInfoDTO;
    }

    /**
     * 注销
     */
    @Override
    public void logout(String token) {
        try {
            String username = tokenUtils.getUsername(token);
            // Redis消息队列发送登录日志消息
            LogLogin logLogin = this.getLoginLog(username, LoginLogEnum.LOGOUT);
            if (logLogin != null) {
                redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_LOG_LOGIN, JSON.toJSONString(logLogin));
            }
            // 删除redis中的在线用户信息
            redisUtils.del(RedisConstant.KEY_ONLINE_USER + username);
        } catch (Exception e) {
            throw new AdminBizException(AdminErrorEnum.ACCOUNT_LOGOUT_ERROR.getCode(), AdminErrorEnum.ACCOUNT_LOGOUT_ERROR.getMsg());
        }
    }

    /**
     * 获取用户菜单
     *
     * @param token token
     * @return List<RouterInfo>
     */
    @Override
    public List<RouterInfoDTO> getRouters(String token) {
        List<RouterInfoDTO> routerInfoDTOS = new ArrayList<>();
        RouterInfoDTO root = new RouterInfoDTO();
        // 获取根节点重定向路由地址
        Setting setting = settingMapper.selectOne(new LambdaQueryWrapper<Setting>().eq(Setting::getName, SettingConstant.SYS_HOME_ROUTER).last(SQLConstant.LIMIT_1));
        root.setRedirect(setting != null ? setting.getValue() : "");
        root.setRouter("root");

        routerInfoDTOS.add(root);
        try {
            String username = tokenUtils.getUsername(token);
            // 根据userId查找用户
            User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username).last(SQLConstant.LIMIT_1));
            if (user == null) {
                throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
            }
            // 获取用户角色
            List<Role> roles = roleMapper.selectByUserId(user.getId());
            // 获取用户的所有菜单
            LinkedHashSet<Menu> menuSet = new LinkedHashSet<>();
            for (Role role : roles) {
                List<Menu> menus = menuMapper.selectByRoleId(role.getId());
                menuSet.addAll(menus);
            }
            List<RouterInfoDTO> routers = getRoutersTree(menuSet);
            root.setChildren(routers);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AdminBizException(AdminErrorEnum.INTERNAL_SERVER_ERROR.getCode(), AdminErrorEnum.INTERNAL_SERVER_ERROR.getMsg());
        }
        return routerInfoDTOS;
    }

    /**
     * 修改密码
     *
     * @param token       token
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     */
    @Override
    public void changePassword(String token, String oldPassword, String newPassword) {
        String username = tokenUtils.getUsername(token);
        // 校验用户名与密码
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username).last(SQLConstant.LIMIT_1));
        Assert.notNull(user, AdminErrorEnum.ACCOUNT_NOT_EXIST_ERROR.getMsg());

        Assert.isTrue(user.getStatus() == 1, AdminErrorEnum.ACCOUNT_DISABLED_ERROR.getMsg());

        boolean oldPasswordFlag = PasswordUtils.valid(oldPassword, user.getPassword());
        Assert.isTrue(oldPasswordFlag, AdminErrorEnum.ACCOUNT_OLD_PWD_ERROR.getMsg());

        boolean passwordRepeatFlag = oldPassword.equals(newPassword);
        Assert.isFalse(passwordRepeatFlag, AdminErrorEnum.ACCOUNT_NEW_PWD_EQUAL_OLD_PWD_ERROR.getMsg());

        user.setPassword(PasswordUtils.encrypt(newPassword));
        userMapper.updateById(user);
    }

    /**
     * 递归生成路由树
     *
     * @param menus 菜单集合
     * @return List<RouterInfo>
     */
    private List<RouterInfoDTO> getRoutersTree(Set<Menu> menus) {
        List<RouterInfoDTO> routers = new ArrayList<>();
        for (RouterInfoDTO router : getParentRoutersTree(menus)) {
            getChildrenRoutersTree(router, menus);
            routers.add(router);
        }
        return routers;
    }

    /**
     * 生成父级路由树
     *
     * @param menus 菜单集合
     * @return List<RouterInfo>
     */
    private List<RouterInfoDTO> getParentRoutersTree(Set<Menu> menus) {
        List<RouterInfoDTO> parentRouters = new ArrayList<>();
        for (Menu menu : menus) {
            if (menu.getParentId() == null) {
                RouterInfoDTO parentRouter = new RouterInfoDTO();
                parentRouter.setId(menu.getId());
                parentRouter.setRouter(menu.getCode());
                parentRouter.setName(menu.getName());
                parentRouter.setPath(menu.getPath());
                parentRouter.setHref(menu.getHref());
                parentRouter.setLink(menu.getLink());
                parentRouter.setIcon(menu.getIcon());
                parentRouter.setSort(menu.getSort());
                parentRouter.setAuthority(menu.getCode());
                parentRouter.setComponent(menu.getComponent());
                parentRouter.setInvisible(menu.getInvisible() == 1);
                parentRouter.setPage(new RouterPageInfoDTO(menu.getClosable() != null && menu.getClosable() == 0));
                parentRouters.add(parentRouter);
            }
        }
        parentRouters.sort(Comparator.comparing(RouterInfoDTO::getSort));
        return parentRouters;
    }

    /**
     * 递归生成子路由树
     *
     * @param parentRouter 上级路由
     * @param menus        菜单集合
     * @return RouterInfo
     */
    private RouterInfoDTO getChildrenRoutersTree(RouterInfoDTO parentRouter, Set<Menu> menus) {
        List<RouterInfoDTO> childrenRouters = new ArrayList<>();
        for (Menu menu : menus) {
            if (menu.getType() != 2 && parentRouter.getId().equals(menu.getParentId())) {
                RouterInfoDTO router = new RouterInfoDTO();
                router.setId(menu.getId());
                router.setRouter(menu.getCode());
                router.setName(menu.getName());
                router.setPath(menu.getPath());
                router.setHref(menu.getHref());
                router.setLink(menu.getLink());
                router.setIcon(menu.getIcon());
                router.setSort(menu.getSort());
                router.setAuthority(menu.getCode());
                router.setComponent(menu.getComponent());
                router.setInvisible(menu.getInvisible() == 1);
                router.setPage(new RouterPageInfoDTO(menu.getClosable() != null && menu.getClosable() == 0));
                childrenRouters.add(getChildrenRoutersTree(router, menus));
            }
        }
        childrenRouters.sort(Comparator.comparing(RouterInfoDTO::getSort));
        parentRouter.setChildren(childrenRouters);
        return parentRouter;
    }

    /**
     * 获取Redis中的用户信息构建登录日志数据
     *
     * @param username 用户名
     * @return 登录日志实体
     */
    private LogLogin getLoginLog(String username, LoginLogEnum loginLogEnum) {
        Map<Object, Object> map = redisUtils.hmget(RedisConstant.KEY_ONLINE_USER + username);
        if (map != null && map.size() > 0) {
            LogLogin logLogin = new LogLogin();
            logLogin.setUsername((String) map.get(UserInfoConstant.KEY_USERNAME));
            logLogin.setToken((String) map.get(UserInfoConstant.KEY_TOKEN));
            logLogin.setType(loginLogEnum.getValue());
            logLogin.setOperationTime(DateUtil.date());
            logLogin.setBrowser((String) map.get(UserInfoConstant.KEY_BROWSER));
            logLogin.setOs((String) map.get(UserInfoConstant.KEY_OS));
            logLogin.setUserIp((String) map.get(UserInfoConstant.KEY_USER_IP));
            logLogin.setTargetIp((String) map.get(UserInfoConstant.KEY_TARGET_IP));
            return logLogin;
        }
        return null;
    }


}
